﻿using System;
using System.IO;
using System.Net;
using System.Text;
using System.Diagnostics;
using System.Xml;

namespace Twitter
{
	/// <summary>
	/// Main client API for interacting with Twitter from an application.
	/// </summary>
	public class BotClient : IBotClient
	{
		static ITraceSource tracer = Tracer.GetSourceFor<BotClient>();

		AuthenticatedEndpoint twitterEndpoint;
		Statuses statuses;
		Messages messages;
		Friendship friendship;
		Uri baseUri;

		/// <summary>
		/// Initializes the client with the given credentials.
		/// </summary>
		/// <param name="credentials">Typically a <see cref="NetworkCredentials"/> instance.</param>
		/// <remarks>Using this overload causes the bot to always use the Twitter API to 
		/// perform all operations.</remarks>
		public BotClient(ICredentials credentials)
			: this(credentials, "http://twitter.com/")
		{
		}

		// TODO: add ctor receiving an IEmailService for message 
		// retrieval.

		/// <summary>
		/// Constructor used for tests to override the base uri 
		/// for twitter requests.
		/// </summary>
		internal BotClient(ICredentials credentials, string baseUriString)
		{
			Guard.ArgumentNotNull(credentials, "credentials");
			Guard.ArgumentNotNullOrEmptyString(baseUriString, "baseUriString");

			Uri baseUri = new Uri(baseUriString);

			this.baseUri = baseUri;
			this.twitterEndpoint = new AuthenticatedEndpoint(credentials);
			this.statuses = new Statuses(twitterEndpoint, baseUri, this);
			// TODO: replace with version that uses email to retrieve new direct messages.
			this.messages = new Messages(twitterEndpoint, baseUri);
			this.friendship = new Friendship(twitterEndpoint, baseUri, this);
		}

		/// <summary>
		/// Exposes operations related to users' statuses.
		/// </summary>
		public IStatuses Statuses
		{
			get { return statuses; }
		}

		/// <summary>
		/// Exposes operations related to users' direct messages.
		/// </summary>
		public IMessages Messages
		{
			get { return messages; }
		}

		/// <summary>
		/// Exposes operations related to users' friends and followers.
		/// </summary>
		public IFriendship Friendship
		{
			get { return friendship; }
		}

		/// <summary>
		/// Gets the profile associated to the given screen name
		/// </summary>
		/// <param name="screenName">user's screen name</param>
		/// <returns>user profile</returns>
		public User GetProfile(string screenName)
		{
			//http://twitter.com/users/show/[screenName].xml
			Uri uri = new Uri(baseUri, String.Format("users/show/{0}.xml", screenName));

			tracer.TraceInformation("Gettting user profile {0} by invoking {1}", screenName, uri);

			WebResponse response = null;
			try
			{
				WebRequest request = twitterEndpoint.CreateRequest(uri);
				response = request.GetResponse();
			}
			catch (WebException wex)
			{
				// TODO: improve error parsing.
				string responseError = new StreamReader(wex.Response.GetResponseStream()).ReadToEnd();
				tracer.TraceError(wex, "Failed to retrieve user profile from {0}. Response was:\n{1}",
					uri,
					responseError);
				throw;
			}
			catch (Exception ex)
			{
				tracer.TraceError(ex, "Failed to retrieve user profile from {0}", uri);
				throw;
			}

			using (response)
			{
				using (XmlReader reader = XmlReader.Create(response.GetResponseStream()))
				{
					return PayloadReader.ReadProfile(reader);
				}
			}
		}

		/// <summary>
		/// Executes the given <paramref name="command"/> for the 
		/// authenticated user.  
		/// </summary>
		/// <param name="command">Command or status update.</param>
		/// <remarks>
		/// Behavior matches that of the Twitter homepage where you 
		/// submit an update, so it can be a simple message to update
		/// the authenticated user status, or any other twitter command, 
		/// such as "follow [friend]", etc. It's not very reliable 
		/// for executing arbitrary commands, though.
		/// </remarks>
		/// <returns><see langword="true"/> if the command was executed successfully; <see langword="false"/> otherwise.</returns>
		internal bool Execute(string command)
		{
			Guard.ArgumentNotNullOrEmptyString(command, "command");

			Tracer.TraceInformation(typeof(BotClient), "Executing command: {0}", command);

			try
			{
				WebRequest request = twitterEndpoint.CreateRequest(new Uri(baseUri, "statuses/update.xml"));
				request.ContentType = "application/x-www-form-urlencoded";
				request.Method = "POST";

				byte[] data = Encoding.UTF8.GetBytes("status=" + command);
				request.ContentLength = data.Length;

				// Submit the data.
				using (Stream requestStream = request.GetRequestStream())
				{
					requestStream.Write(data, 0, data.Length);

					using (WebResponse response = request.GetResponse())
					using (Stream responseStream = response.GetResponseStream())
					{
						// throw if an error happened.
						string result = new StreamReader(responseStream).ReadToEnd();
					}
				}

				return true;
			}
			catch (WebException wex)
			{
				// TODO: improve error parsing.
				string responseError = new StreamReader(wex.Response.GetResponseStream()).ReadToEnd();
				Tracer.TraceError(typeof(AuthenticatedEndpoint), wex, "Failed to execute command {0}. Response was:\n{1}",
					command,
					responseError);
				throw;
			}
			catch (Exception ex)
			{
				Tracer.TraceError(typeof(BotClient), ex, "Failed to execute command: {0}", command);
				return false;
			}
		}

		public bool Ping()
		{
			try
			{
				WebRequest request = twitterEndpoint.CreateRequest(new Uri(baseUri, "help/test.json"));

				using (WebResponse response = request.GetResponse()) 
				{
					return response is HttpWebResponse &&
						((HttpWebResponse)response).StatusCode == HttpStatusCode.OK;
				}
			}
			catch (WebException wex)
			{
				// TODO: improve error parsing.
				string responseError = "";
				if (wex.Response != null)
					responseError = " Response was:\n" + new StreamReader(wex.Response.GetResponseStream()).ReadToEnd();

				Tracer.TraceError(typeof(AuthenticatedEndpoint), wex, "Failed to ping Twitter.{0}",
					responseError);

				return false;
			}
		}
	}
}
